package com.googlecode.horncomb.web.spring.mvc;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

import com.googlecode.horncomb.util.lang.GenericsUtil;
import com.googlecode.horncomb.web.util.ModelAndViewUtil;

/**
 * 扩展Sping MVC的SimpleFormController
 * @author Daniel
 */
public class BaseSimpleFormController extends SimpleFormController {
	public final static String PREFIX_REDIRECTVIEW = "redirect"; //$NON-NLS-1$
	public final static String VIEW_TYPE_TOKEN = ":"; //$NON-NLS-1$
	protected BackingObjectFactory backingObjectFactory = null;
	protected String urlEncoding = "UTF-8"; //$NON-NLS-1$
	private OnBindingAction[] onBindAndValidateActions;
	private OnBindingAction[] bindOnNewFormActions;
	/**
	 * 日期格式
	 */
	// protected String dateFormate = "yyyy-MM-dd HH:mm:ss";
	protected String dateFormate = "yyyy-MM-dd";

	public BaseSimpleFormController() {
		super();
		// 新表单（get）时候也进行构造
		this.setBindOnNewForm(true);
	}

	@Override
	protected void initApplicationContext() {
		super.initApplicationContext();
		// modify command to "form"
		if (DEFAULT_COMMAND_NAME.equals(this.getCommandName())
				|| !StringUtils.hasText(this.getCommandName())) {
			this.setCommandName("form");
		}

		if (this.getCommandClass() == null) {
			Class<?> type = GenericsUtil.getGenericClass(this.getClass());
			if (type != null) {
				this.setCommandClass(type);
			}
		}

		if (this.getCommandClass() == null) {
			throw new IllegalStateException(
					"Cannot create command without commandClass being set - "
							+ "either set commandClass or (in a form controller) override formBackingObject");
		}
	}

	/**
	 * 初始化绑定日期、数值编辑器
	 * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder(javax.servlet.http.HttpServletRequest,
	 *      org.springframework.web.bind.ServletRequestDataBinder)
	 */
	@Override
	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws Exception {
		// 允许为空的绑定
		// bind DateEditor
		SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormate);
		binder.registerCustomEditor(Date.class, new CustomDateEditor(
				dateFormat, true));
		// bind IntegerEditor
		binder.registerCustomEditor(Integer.class, new CustomNumberEditor(
				Integer.class, true));
		// bind LongEditor
		binder.registerCustomEditor(Long.class, new CustomNumberEditor(
				Long.class, true));
		// bind DoubleEditor
		binder.registerCustomEditor(Double.class, new CustomNumberEditor(
				Double.class, true));
		// bind ByteArrayMultipartFileEditor for file upload
		binder.registerCustomEditor(byte[].class,
				new ByteArrayMultipartFileEditor());
	}

	/**
	 * {@inheritDoc}
	 */
	protected Object formBackingObject(HttpServletRequest request)
			throws Exception {
		// Object sessionFormObject = null;
		// if (this.isSessionForm()) {
		// HttpSession session = request.getSession(false);
		// if (session == null) {
		// throw new HttpSessionRequiredException(
		// "Must have session when trying to bind (in session-form mode)");
		// }
		// String formAttrName = getFormSessionAttributeName(request);
		// sessionFormObject = session.getAttribute(formAttrName);
		//
		// }
		// if (sessionFormObject != null) {
		// return sessionFormObject;
		// }
		if (this.backingObjectFactory != null) {
			return this.backingObjectFactory.createBackingObject(request,
					this.getCommandClass());
		} else {
			return super.formBackingObject(request);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected ModelAndView onSubmit(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception {
		ModelAndView mv = onSubmit(command);
		if (mv != null) {
			// simplest onSubmit variant implemented in custom subclass
			return mv;
		} else {
			return this.getSuccessModeAndView(request, command, errors, null);
		}
	}

	@SuppressWarnings("unchecked")
	protected ModelAndView getSuccessModeAndView(HttpServletRequest request,
			Object command, BindException errors, Map returnModel)
			throws Exception {
		if (StringUtils.hasText(getSuccessView())
				&& getSuccessView().equals(getFormView())) {
			this.setSuccessView(null);
		}

		if (returnModel == null) {
			returnModel = errors.getModel();
		} else {
			returnModel.putAll(errors.getModel());
		}

		// 原则上优先服从successView，然后是formView
		if (StringUtils.hasText(getSuccessView())) {
			ModelAndView mav = ModelAndViewUtil.createModelAndView(
					getSuccessView(), returnModel);
			Map<String, Object> ref = this.getSuccessViewReference(request,
					command, returnModel, errors);
			if (ref != null) {
				mav.addAllObjects(ref);
			}
			return mav;
		} else if (getFormView() != null) {
			// RedirectView view = new RedirectView(getFormView() + ".shtml");
			// return new ModelAndView(view, returnModel);
			return showForm(request, errors, getFormView(), returnModel);
		} else {
			throw new ServletException("formView, successView aren't set");
		}
	}

	protected Map<String, Object> getSuccessViewReference(
			HttpServletRequest request, Object command,
			Map<String, Object> returnModel, Errors error) throws Exception {
		Map<String, Object> ref = new HashMap<String, Object>();
		return ref;
	}

	@Override
	protected void onBindAndValidate(HttpServletRequest request,
			Object command, BindException errors) throws Exception {
		if (this.onBindAndValidateActions != null
				&& this.onBindAndValidateActions.length > 0) {
			for (OnBindingAction abav : onBindAndValidateActions) {
				this.invokeOnBindingAction(abav, request, command, errors);
			}
		} else {
			super.onBindAndValidate(request, command, errors);
		}
	}

	@Override
	protected void onBindOnNewForm(HttpServletRequest request, Object command,
			BindException errors) throws Exception {
		if (!this.isFormSubmission(request)) {
			// get command data on new form
			this.referenceCommand(request, command);
		}
		if (this.bindOnNewFormActions != null
				&& this.bindOnNewFormActions.length > 0) {
			for (OnBindingAction abav : bindOnNewFormActions) {
				this.invokeOnBindingAction(abav, request, command, errors);
			}
		} else {
			super.onBindOnNewForm(request, command, errors);
		}
	}

	protected void referenceCommand(HttpServletRequest request, Object command)
			throws Exception {

	}

	private void invokeOnBindingAction(OnBindingAction abav,
			HttpServletRequest request, Object command, BindException errors)
			throws Exception {
		Assert.notNull(command, "command must not be null");
		Assert.notNull(errors, "Errors object must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Invoking validator [" + abav + "]");
		}
		abav.doOnBind(request, command, errors);
	}

	public void setDateFormate(String dateFormate) {
		this.dateFormate = dateFormate;
	}

	/**
	 * @param urlEncoding
	 *            the urlEncoding to set
	 */
	public void setUrlEncoding(String urlEncoding) {
		this.urlEncoding = urlEncoding;
	}

	/**
	 * @param backingObjectFactory
	 *            the backingObjectFactory to set
	 */
	public void setBackingObjectFactory(
			BackingObjectFactory backingObjectFactory) {
		this.backingObjectFactory = backingObjectFactory;
	}

	/**
	 * @param bindOnNewFormActions
	 *            the bindOnNewFormActions to set
	 */
	public void setBindOnNewFormActions(OnBindingAction[] bindOnNewFormActions) {
		this.bindOnNewFormActions = bindOnNewFormActions;
	}

	/**
	 * @param onBindAndValidateActions
	 *            the onBindAndValidateActions to set
	 */
	public void setOnBindAndValidateActions(
			OnBindingAction[] onBindAndValidateActions) {
		this.onBindAndValidateActions = onBindAndValidateActions;
	}
}
